--- title: "03-缓存池机制(Cache Pool)" created: 2025-11-25 tags: - Java --- # 缓存池机制(Cache Pool) ## 核心概念 Java为了提升性能和节省内存,在包装类中实现了**对象复用机制**:对于常用的数值范围,JVM会提前创建对象并缓存,多次使用时返回同一个对象,而不是重复创建。 --- ## 一、为什么需要缓存池? ### 1.1 问题背景 ```java // 假设没有缓存池 Integer a = 10; // 创建对象1 Integer b = 10; // 创建对象2 Integer c = 10; // 创建对象3 // 小数值频繁使用,会创建大量重复对象,浪费内存 ``` ### 1.2 设计动机 根据实践统计,**大部分数值操作集中在 -128 ~ 127 范围内**: - 循环计数器:`for (int i = 0; i < 100; i++)` - 状态码:HTTP状态码200、404等 - 业务标识:1表示成功、0表示失败 **缓存池策略**:提前创建这些常用对象,减少内存分配和GC压力。 --- ## 二、Integer缓存池实现 ### 2.1 源码分析(JDK 8) ```java public static Integer valueOf(int i) { // 判断是否在缓存范围内 if (i >= IntegerCache.low && i <= IntegerCache.high) return IntegerCache.cache[i + (-IntegerCache.low)]; // 返回缓存对象 return new Integer(i); // 超出范围,创建新对象 } // 内部缓存类 private static class IntegerCache { static final int low = -128; // 下限固定 static final int high; // 上限可配置 static final Integer cache[]; // 缓存数组 static { // 默认上限127,可通过JVM参数调整 int h = 127; String integerCacheHighPropValue = sun.misc.VM.getSavedProperty("java.lang.Integer.IntegerCache.high"); if (integerCacheHighPropValue != null) { try { int i = parseInt(integerCacheHighPropValue); i = Math.max(i, 127); // 最小不低于127 h = Math.min(i, Integer.MAX_VALUE - 129); // 最大上限 } catch( NumberFormatException nfe) { } } high = h; // 预先创建缓存对象 cache = new Integer[(high - low) + 1]; int j = low; for(int k = 0; k < cache.length; k++) cache[k] = new Integer(j++); // 填充-128到high的所有Integer对象 } } ``` ### 2.2 工作流程 ``` 调用Integer.valueOf(100) ↓ 判断: 100 >= -128 && 100 <= 127 ↓ true 返回: IntegerCache.cache[100 - (-128)] // 下标228 ↓ 返回缓存的同一个对象 ``` ### 2.3 典型案例 ```java // 案例1:缓存范围内 Integer a = 127; Integer b = 127; System.out.println(a == b); // true(同一对象) // 案例2:缓存范围外 Integer x = 128; Integer y = 128; System.out.println(x == y); // false(两个不同对象) // 案例3:混合场景 Integer m = Integer.valueOf(127); // 缓存对象 Integer n = new Integer(127); // 强制新建对象 System.out.println(m == n); // false(不同对象) ``` --- ## 三、其他包装类的缓存机制 ### 3.1 各类型缓存范围 | 包装类 | 缓存范围 | 是否可配置 | | --- | --- | --- | | **Byte** | -128 ~ 127(全部值) | ❌ 固定 | | **Short** | -128 ~ 127 | ❌ 固定 | | **Integer** | -128 ~ 127 | ✅ 可配置上限 | | **Long** | -128 ~ 127 | ❌ 固定 | | **Character** | \u0000 ~ \u007F(0~127) | ❌ 固定 | | **Boolean** | true / false | ❌ 固定(仅2个对象) | | **Float** | 无缓存 | ❌ 小数太多 | | **Double** | 无缓存 | ❌ 小数太多 | ### 3.2 为什么Float和Double没有缓存? ```java // 浮点数在一个很小的范围内都有无穷多个值 // 例如:0.1, 0.11, 0.111, 0.1111, ... // 无法预测哪些值会被频繁使用,缓存意义不大 ``` ### 3.3 Boolean的特殊实现 ```java public static final Boolean TRUE = new Boolean(true); public static final Boolean FALSE = new Boolean(false); public static Boolean valueOf(boolean b) { return (b ? TRUE : FALSE); // 永远返回这两个对象之一 } // 使用示例 Boolean a = true; // 指向Boolean.TRUE Boolean b = true; // 指向Boolean.TRUE System.out.println(a == b); // true ``` --- ## 四、new Integer() vs Integer.valueOf() ### 4.1 核心区别 ```java // 方式1:new关键字(不推荐) Integer a = new Integer(100); // ❌ 强制创建新对象,不走缓存 // 方式2:valueOf方法(推荐) Integer b = Integer.valueOf(100); // ✅ 优先使用缓存 // 方式3:自动装箱(本质调用valueOf) Integer c = 100; // ✅ 等价于Integer.valueOf(100) ``` ### 4.2 对比验证 ```java Integer a = new Integer(100); Integer b = new Integer(100); Integer c = 100; Integer d = 100; System.out.println(a == b); // false(new强制创建不同对象) System.out.println(c == d); // true(缓存池同一对象) System.out.println(a == c); // false(一个是new的,一个是缓存的) ``` ### 4.3 最佳实践 ```java // ✅ 推荐写法 Integer num = 100; // 自动装箱,走缓存 Integer num2 = Integer.valueOf(100); // 显式调用,走缓存 // ❌ 过时写法(JDK 9已标记@Deprecated) Integer num3 = new Integer(100); // 强制创建,浪费内存 ``` --- ## 五、如何调整Integer缓存范围? ### 5.1 JVM参数配置 ```java # 将Integer缓存上限扩展到1000 java -XX:AutoBoxCacheMax=1000 YourApp # 验证效果 Integer a = 500; Integer b = 500; System.out.println(a == b); // 调整后为true ``` ### 5.2 适用场景 ```java // 业务场景:用户ID通常在1~10000之间 // 默认缓存(-128~127):大量ID会创建新对象 // 扩展缓存(-128~10000):大幅减少对象创建 // 配置启动参数 // -XX:AutoBoxCacheMax=10000 ``` ### 5.3 注意事项 - ⚠️ **仅Integer可配置**,其他类型缓存范围固定 - ⚠️ **只能调整上限**,下限固定为-128 - ⚠️ **上限不能超过** Integer.MAX\_VALUE - 129 - ⚠️ **增大缓存会占用更多内存**,需权衡 --- ## 六、缓存池的应用与陷阱 ### 6.1 正确使用 ```java // ✅ 利用缓存提升性能 public void countFrequency(List numbers) { Map map = new HashMap<>(); for (Integer num : numbers) { // 小数值频繁使用,自动走缓存,减少对象创建 map.put(num, map.getOrDefault(num, 0) + 1); } } ``` ### 6.2 常见陷阱 #### 陷阱1:误用 == 比较 ```java Integer a = 200; Integer b = 200; if (a == b) { // ❌ false,超出缓存范围 // 永远不会执行 } // 正确写法 if (a.equals(b)) { // ✅ true // 正常执行 } ``` #### 陷阱2:数据库ID比较 ```java // 数据库返回的ID通常 > 127 Integer userId1 = userDao.getUserId("user1"); // 假设返回1001 Integer userId2 = userDao.getUserId("user2"); // 假设返回1001 if (userId1 == userId2) { // ❌ false(不同对象) // 判断失败 } // 正确写法 if (userId1.equals(userId2)) { // ✅ true // 正常执行 } ``` #### 陷阱3:Set去重失效 ```java Set set = new HashSet<>(); set.add(new Integer(100)); // 强制新建对象 set.add(new Integer(100)); // 又新建一个对象 // 虽然值相同,但HashSet通过equals()判断,能正确去重 System.out.println(set.size()); // 1(正确去重) // 但如果自定义类未重写equals/hashCode class User { int id; User(int id) { this.id = id; } } Set users = new HashSet<>(); users.add(new User(100)); users.add(new User(100)); System.out.println(users.size()); // 2(去重失败!) ``` --- ## 七、高频面试题 ### Q1: Java中的缓存池机制是怎么实现的? **答:** - 在包装类的静态内部类中,预先创建常用范围的对象数组 - 调用`valueOf()`时,先判断是否在缓存范围 - 范围内返回缓存对象,范围外创建新对象 ### Q2: 哪些类型用到了缓存池?范围分别是什么? **答:** - **整型**:Byte/Short/Integer/Long → -128 ~ 127 - **字符型**:Character → 0 ~ 127(ASCII字符) - **布尔型**:Boolean → true/false(仅2个对象) - **浮点型**:Float/Double → 无缓存 ### Q3: Integer缓存池的范围可以调整吗?如何调整? **答:** - **可以**,通过JVM参数 `-XX:AutoBoxCacheMax=1000` - **仅能调整上限**,下限固定-128 - **其他类型不可配置** ### Q4: 为什么127之内的Integer相等,而超过127的不相等? **答:** ```java Integer a = 127; Integer b = 127; a == b; // true(缓存池同一对象) Integer x = 128; Integer y = 128; x == y; // false(超出缓存,两个不同对象) ``` ### Q5: new Integer(123) 和 Integer.valueOf(123) 有什么区别? **答:** | 方式 | 是否使用缓存 | 性能 | 推荐度 | | --- | --- | --- | --- | | `new Integer(123)` | ❌ 强制创建新对象 | 差 | 已废弃 | | `Integer.valueOf(123)` | ✅ 优先缓存 | 好 | 推荐 | | `Integer num = 123` | ✅ 自动调用valueOf | 好 | 推荐 | ### Q6: 为什么Float和Double没有缓存池? **答:** - 浮点数在任意区间都有无穷多个值(如0.1、0.11、0.111...) - 无法预测哪些值会被频繁使用 - 缓存所有浮点数不现实,缓存少部分意义不大 --- ## 八、最佳实践 ### ✅ 推荐做法 ```java // 1. 优先使用自动装箱 Integer num = 100; // 而非 new Integer(100) // 2. 包装类比较统一用equals() Integer a = 200; Integer b = 200; if (a.equals(b)) { } // 而非 a == b // 3. 理解缓存范围,避免误判 if (num >= -128 && num <= 127) { // 此范围内的Integer对象可能被缓存复用 } ``` ### ❌ 避免做法 ```java // 1. 不要用new创建包装对象 Integer num = new Integer(100); // 已废弃 // 2. 不要用 == 比较包装类 Integer a = 200; Integer b = 200; if (a == b) { } // 错误,应该用equals() // 3. 不要依赖缓存池做业务逻辑 if (num1 == num2) { // 危险!可能因超出缓存范围而失败 // 业务逻辑 } ``` --- ## 相关链接 - [[01-基本类型与包装类|01-基本类型与包装类]] - 8种基本类型与包装类对应关系 - [[02-自动装拆箱|02-自动装拆箱]] - valueOf()和intValue()的调用时机 - [[02-HashMap源码解析|HashMap原理]] - equals()和hashCode()如何影响缓存池对象的使用 --- ## 记忆口诀 > 缓存池范围负一二八到一二七 > Integer上限可调整,其余固定莫改变 > valueOf走缓存,new方法强制建 > 包装比较用equals,不要依赖双等号 --- ⬅️ [[02-自动装拆箱|自动装拆箱]] 🏠 [[00-Java|00-Java]] ➡️ [[04-类型转换规则|类型转换规则]]